package net.rvb1.gwt.grad.ui.client.cellview.pager;

import com.google.gwt.event.dom.client.ScrollEvent;
import com.google.gwt.event.dom.client.ScrollHandler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.cellview.client.HasKeyboardPagingPolicy;
import com.google.gwt.user.cellview.client.HasKeyboardPagingPolicy.KeyboardPagingPolicy;
import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy;
import com.google.gwt.user.client.ui.HasScrolling;
import com.google.gwt.view.client.HasData;
import com.google.gwt.view.client.HasRows;
import com.google.gwt.view.client.ProvidesKey;
import com.google.gwt.view.client.Range;

import java.util.List;

/**
 * This {@link RangeLabelPager} only accepts displays that implements {@link HasScrolling},
 * {@link HasData}, {@link HasKeyboardPagingPolicy}.
 * <P>
 * Every time the display's scroller has hit it's end then we call
 * {@link HasRows#setVisibleRange(com.google.gwt.view.client.Range)} incrementing the display's
 * visible range using {@link #getIncrementSize()}.<BR/>
 * </P>
 * 
 * @see #setDisplay(HasData)
 */
public class ScrollPager<T extends HasData<?> & HasKeyboardPagingPolicy & HasScrolling> extends
    RangeLabelPager<T> {

  public static abstract class AsyncDataProvider<T> extends
      com.google.gwt.view.client.AsyncDataProvider<T> {

    private ScrollPager<?> pager;

    public AsyncDataProvider(ScrollPager<?> pager) {
      super();
      this.pager = pager;
    }

    public AsyncDataProvider(ScrollPager<?> pager, ProvidesKey<T> keyProvider) {
      super(keyProvider);
      this.pager = pager;
    }

    protected abstract List<T> getData(int start, int end);

    @Override
    protected final void onRangeChanged(HasData<T> display) {
      final Range visibleRange = display.getVisibleRange();
      final int visibleRangeEnd = visibleRange.getStart() + visibleRange.getLength();
      final int realStart =
          (visibleRange.getLength() != pager.getIncrementSize()) ? visibleRangeEnd : 0;
      updateRowData(display, realStart, getData(realStart, realStart + pager.getIncrementSize()));
    }
  }

  public static final class ListDataProvider<T> extends
      com.google.gwt.view.client.ListDataProvider<T> {
    private ScrollPager<?> pager;

    public ListDataProvider(ScrollPager<?> pager) {
      super();
      this.pager = pager;
    }

    public ListDataProvider(ScrollPager<?> pager, List<T> listToWrap) {
      super(listToWrap);
      this.pager = pager;
    }

    public ListDataProvider(ScrollPager<?> pager, List<T> listToWrap, ProvidesKey<T> keyProvider) {
      super(listToWrap, keyProvider);
      this.pager = pager;
    }

    public ListDataProvider(ScrollPager<?> pager, ProvidesKey<T> keyProvider) {
      super(keyProvider);
      this.pager = pager;
    }

    @Override
    protected final void onRangeChanged(HasData<T> display) {
      final List<T> list = getList();
      int size = list.size();
      if (size > 0) {
        // Do not push data if the data set is empty.
        final Range visibleRange = display.getVisibleRange();
        final int end = visibleRange.getLength();
        final int start = end - pager.getIncrementSize();
        updateRowData(display, start, list.subList(start, end));
      }
    }
  }

  /**
   * The default increment size.
   */
  private static final int DEFAULT_INCREMENT = 20;

  /**
   * The increment size.
   */
  private int incrementSize = DEFAULT_INCREMENT;

  /**
   * The last scroll position.
   */
  private int lastScrollPos = 0;

  private HandlerRegistration scrollHandlerRegistration;

  /**
   * Get the number of rows by which the range is increased when the scrollbar reaches the bottom.
   * 
   * @return the increment size
   */
  public int getIncrementSize() {
    return this.incrementSize;
  };

  public void resetLastScrollPos() {
    lastScrollPos = 0;
  }

  /**
   * {@inheritDoc}
   * 
   * <P>
   * This {@link HasRows} has to implement {@link HasScrolling}, {@link HasData} and
   * {@link HasKeyboardPagingPolicy}.
   * </P>
   * <P>
   * The following display's attributes will be initialized :<BR/>
   * <UL>
   * <LI>{@link KeyboardPagingPolicy} will be set to {@link KeyboardPagingPolicy#INCREASE_RANGE},</LI>
   * <LI>{@link KeyboardSelectionPolicy} to {@link KeyboardSelectionPolicy#BOUND_TO_SELECTION},</LI>
   * <LI>the visible range will be set from 0 (start) to {@link #getIncrementSize()} (end)</LI>
   * </UL>
   * </P>
   * <P>
   * Every time the display's scroller has hit it's end, then we call
   * {@link HasRows#setVisibleRange(com.google.gwt.view.client.Range)} incrementing the display's
   * visible range by using {@link #getIncrementSize()}.<BR/>
   * </P>
   * 
   * @see HasKeyboardPagingPolicy#setKeyboardPagingPolicy(KeyboardPagingPolicy)
   * @see HasKeyboardPagingPolicy#setKeyboardSelectionPolicy(KeyboardSelectionPolicy)
   * @see HasRows#setVisibleRange(int, int)
   */
  @Override
  public void setDisplay(final T display) {
    if (this.scrollHandlerRegistration != null) {
      this.scrollHandlerRegistration.removeHandler();
    }
    super.setDisplay(display);

    if (display != null) {
      ((HasKeyboardPagingPolicy) display)
          .setKeyboardPagingPolicy(KeyboardPagingPolicy.INCREASE_RANGE);
      ((HasKeyboardPagingPolicy) display)
          .setKeyboardSelectionPolicy(KeyboardSelectionPolicy.BOUND_TO_SELECTION);

      display.setVisibleRange(0, getIncrementSize());

      this.scrollHandlerRegistration =
          ((HasScrolling) display).addScrollHandler(new ScrollHandler() {
            @Override
            public void onScroll(ScrollEvent event) {
              // If scrolling up, ignore the event.
              int oldScrollPos = ScrollPager.this.lastScrollPos;
              ScrollPager.this.lastScrollPos = ((HasScrolling) display).getVerticalScrollPosition();
              if (oldScrollPos >= ScrollPager.this.lastScrollPos) {
                return;
              }

              HasRows display = getDisplay();
              if (display == null) {
                return;
              }

              int maxScrollTop = ((HasScrolling) display).getMaximumVerticalScrollPosition();
              if (ScrollPager.this.lastScrollPos >= maxScrollTop) {
                // We are near the end, so increase the page size.
                int newPageSize =
                    Math.min(
                        display.getVisibleRange().getLength() + ScrollPager.this.incrementSize,
                        display.getRowCount());
                display.setVisibleRange(0, newPageSize);
              }
            }
          });
    }
  }

  /**
   * Set the number of rows by which the range is increased when the scrollbar reaches the bottom.
   * 
   * @param incrementSize the incremental number of rows
   */
  public void setIncrementSize(int incrementSize) {
    this.incrementSize = incrementSize;
  }
}
